﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.IO;
using FourKillEnginePrototype.Properties;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;

namespace FourKillEnginePrototype
{
    public class FirstPersonViewControl : Control
    {
        private Random r = new Random();
        private ImageList spriteImageList;
        private System.ComponentModel.IContainer components;
        private const double RADIAN_CONVERT = Math.PI/180.0d;
        private const float _beginAlphaDistance = 5.0f;
        private List<Polygon3D> _polygons = new List<Polygon3D>();
        private int _grassOffset;

        public void MoveForward(double distance)
        {
            _player.Move(distance);
            _grassOffset++;
            this.Invalidate();
        }

        public void Levitate(double distance)
        {
            _player.Levitate(distance);
            this.Invalidate();
        }

        public void MoveSideways(double distance)
        {
            _player.SideStep(distance);
            this.Invalidate();
        }

        public void TurnDegrees(double degrees)
        {
            _player.TurnDegrees(degrees);
            this.Invalidate();
        }

        public void RandomlyMoveSprites()
        {
            _sprites.ForEach(s => {
                //i++;
                if (s.SpriteType == SpriteType.Person)
                {
                    var randomNum = r.Next(0, 100);

                    s.Move(r.Next(1, 3));
                    s.TurnDegrees(r.Next(1, 10));
                }
                else
                {
                    // Always move clouds perpendicular to player angle
                    s.OrientationAngle = _player.OrientationAngle - (90 * RADIAN_CONVERT);
                    s.Move(s.MovementSpeed);
                }
            });
            this.Invalidate();
        }

        private ViewPort _viewPort = new ViewPort 
        {
            Width = 6,
            HorizontalViewableAngle = 90 * RADIAN_CONVERT,
            VerticalViewableAngle = 45 * RADIAN_CONVERT
        };

        private Player _player = new Player
        {
            Width = 3,
            Height = 6.25,
            LocationX = 30,
            LocationY = 20,
            OrientationAngle = 90 * RADIAN_CONVERT
        };

        private static List<SimpleSpriteSheet> _spriteSheets;

        private List<Sprite> _sprites = new List<Sprite>();

        private Brush _skyBrush = Brushes.SkyBlue;
        private Brush _groundBrush = new SolidBrush(Color.FromArgb(25, 97, 11));
        private Brush _shadowBrush = new SolidBrush(Color.FromArgb(75, 15, 87, 1));

        private void DrawFirstPersonView(Graphics g)
        {
            try
            {

                double scale = this.Width / this._viewPort.Width;

                int viewPortWidth = Convert.ToInt32(this._viewPort.Width * scale);
                int viewPortHeight = Convert.ToInt32(this._viewPort.Height * scale);

                this.Width = viewPortWidth;
                this.Height = viewPortHeight;

                // Draw Grass
                for (int y = (int)(Size.Height / 2); y < Size.Height; y++)
                {
                    Pen p = new Pen(GetYColorFor(y, viewPortHeight / 2, _grassOffset, scale));
                    g.DrawLine(p, 0, y, viewPortWidth, y);
                    p.Dispose();
                }

                foreach (Polygon3D poly in _polygons)
                {
                    Point[] points = new Point[poly.Points.Count];
                    poly.UpdateRelativeCoordinates(_player.LocationX, _player.LocationY, _player.OrientationAngle);
                    int x = 0, y = 0;
                    int? prevX = null, prevY = null, firstX = null, firstY = null;
                    for (int i=0; i<poly.Points.Count;i++)
                    {
                        Point3D point = poly.Points[i];
                        //double groundLocationInView = CalculateViewLocationRelativeToGround(_player.CameraHeight, 0, point.RelativeDistance, _viewPort.DistanceToView);
                        double verticalLocationInView = CalculateViewLocationRelativeToGround(_player.CameraHeight, point.Z, point.RelativeDistance, _viewPort.DistanceToView);
                        //double topLocationInView = CalculateViewLocationRelativeToGround(_player.CameraHeight, point.Z, point.RelativeDistance, _viewPort.DistanceToView);
                        double horizontalLocationInView = CalculateViewLocationRelativeToGround(0, Math.Abs(point.RelativeY), point.RelativeX, _viewPort.DistanceToView);

                        int horizontalOffset = Convert.ToInt32((horizontalLocationInView * scale) * (point.RelativeAngleToX >= 0 ? -1 : 1));

                        y = (this.Height / 2) + Convert.ToInt32((_player.CameraHeight - verticalLocationInView) * scale);
                        x = (this.Width / 2) + horizontalOffset;

                        
                        //if (prevX.HasValue && prevY.HasValue)
                        //{
                        //    g.DrawLine(Pens.OrangeRed, prevX.Value, prevY.Value, x, y);
                        //}

                        points[i] = new Point(x, y);
                        prevX = x;
                        prevY = y;
                        if (!firstX.HasValue)
                        {
                            firstX = x;
                            firstY = y;
                        }
                    }
                    using (Brush brush = new SolidBrush(Color.FromArgb(128, r.Next(0, 255), r.Next(0, 255), r.Next(0, 255))))
                    {
                        g.DrawPolygon(Pens.Black, points);
                        g.FillPolygon(brush, points);
                    }
                    //if (firstX.HasValue && firstY.HasValue)
                    //{
                    //    g.DrawLine(Pens.OrangeRed, firstX.Value, firstY.Value, x, y);
                    //}
                    //using (Brush brush = new SolidBrush(Color.FromArgb(128, r.Next(0, 255), r.Next(0, 255), r.Next(0, 255))))
                    //{
                    //    g.DrawPolygon(Pens.Black, points);
                    //    g.FillPolygon(brush, points);
                    //    try
                    //    {
                    //        if (points.Length == 4)
                    //        {
                    //            List<Point> l = new List<Point>();
                    //            l.Add(points[3]);
                    //            l.Add(points[1]);
                    //            l.Add(points[2]);
                    //            //g.DrawImage(Properties.Resources.bricks, l.ToArray());
                    //        }
                    //        else
                    //        {
                    //            //g.DrawImage(Properties.Resources.bricks, points);
                    //        }
                    //    }
                    //    catch (Exception ex) 
                    //    {
                    //        var dd = ex.Message;
                    //    }
                    //}
                }

                _sprites.ForEach(s => s.UpdateRelativeCoordiantes(_player.LocationX, _player.LocationY, _player.OrientationAngle));

                List<Tuple<Rectangle, Sprite>> spriteRects = new List<Tuple<Rectangle, Sprite>>();
                foreach (Sprite sprite in _sprites.Where(s => !s.IsBehindPlayer).OrderByDescending(e => e.RelativeDistance))
                {

                    double groundLocationInView = CalculateViewLocationRelativeToGround(_player.CameraHeight, 0, sprite.RelativeDistance, _viewPort.DistanceToView);
                    double bottomLocationInView = CalculateViewLocationRelativeToGround(_player.CameraHeight, sprite.Levitation, sprite.RelativeDistance, _viewPort.DistanceToView);
                    double topLocationInView = CalculateViewLocationRelativeToGround(_player.CameraHeight, (sprite.Levitation + sprite.Height), sprite.RelativeDistance, _viewPort.DistanceToView);
                    double horizontalOffsetInView = CalculateViewLocationRelativeToGround(0, Math.Abs(sprite.RelativeY), sprite.RelativeX, _viewPort.DistanceToView);

                    if (groundLocationInView == double.NaN || bottomLocationInView == double.NaN || topLocationInView == double.NaN || horizontalOffsetInView == double.NaN)
                    {
                        //System.Diagnostics.Debugger.Break();
                        continue;
                    }

                    int x, y, h, w, groundY, horizontalOffset = Convert.ToInt32(horizontalOffsetInView * scale) * (sprite.RelativeAngleToX >= 0 ? -1 : 1);

                    h = Convert.ToInt32((topLocationInView - bottomLocationInView) * scale);
                    w = Convert.ToInt32((sprite.Width * h) / sprite.Height);

                    y = (this.Height / 2) + Convert.ToInt32((_player.CameraHeight - topLocationInView) * scale);
                    x = (this.Width / 2) - (w / 2) + horizontalOffset;

                    groundY = (this.Height / 2) + Convert.ToInt32((_player.CameraHeight - groundLocationInView) * scale);

                    if(sprite.SpriteType== SpriteType.Person)
                    g.FillEllipse(_shadowBrush, new Rectangle(x, groundY - w / 12, w, w / 6));
                    spriteRects.Add(new Tuple<Rectangle, Sprite>(new Rectangle(x, y, w, h), sprite));

                }

                // Sky

                // Draw Sky
                for (int x = 0; x < Size.Width; x++)
                {
                    Pen p = new Pen(GetXColorFor(x - Convert.ToInt32(_player.OrientationAngle * 180), Size.Width / 6, Color.SkyBlue, Color.LightBlue));
                    g.DrawLine(p, x, 0, x, Size.Height / 2);
                    p.Dispose();
                }

                foreach (Tuple<Rectangle, Sprite> t in spriteRects)
                {
                    using (Brush spriteBrush = new SolidBrush(t.Item2.Color))
                    {
                        if (t.Item2.SpriteType == SpriteType.Person || t.Item2.SpriteType == SpriteType.Fixed)
                        {
                            if (t.Item2.RelativeDistance < _beginAlphaDistance)
                            {
                                ColorMatrix cm = new ColorMatrix();
                                cm.Matrix00 = cm.Matrix11 = cm.Matrix22 = cm.Matrix44 = 1;
                                cm.Matrix33 = (float)t.Item2.RelativeDistance / _beginAlphaDistance;

                                using (ImageAttributes ia = new ImageAttributes())
                                {
                                    ia.SetColorMatrix(cm);
                                    g.DrawImage(t.Item2.CurrentImage(t.Item1.Width, t.Item1.Height),
                                    new Rectangle(t.Item1.X, t.Item1.Y, t.Item1.Width, t.Item1.Height), 0, 0, t.Item1.Width, t.Item1.Height, GraphicsUnit.Pixel, ia);
                                }
                            }
                            else
                            {
                                g.DrawImage(t.Item2.CurrentImage(t.Item1.Width, t.Item1.Height), t.Item1);
                            }
                        }
                        else if(t.Item2.SpriteType == SpriteType.Cloud)
                        {
                            g.SmoothingMode = SmoothingMode.AntiAlias;
                            using (SolidBrush sb = new SolidBrush(t.Item2.Color))
                            {
                                g.FillPath(sb, CreateRoundedRectanglePath(t.Item1, 8));
                            }
                        }
                        else if (t.Item2.SpriteType == SpriteType.Vegetation)
                        {
                            Point p1 = new Point(t.Item1.X + t.Item1.Width / 2, t.Item1.Y);
                            Point p2 = new Point(t.Item1.X, t.Item1.Y + t.Item1.Height);
                            Point p3 = new Point(t.Item1.X + t.Item1.Width, t.Item1.Y + t.Item1.Height);
                            g.FillPolygon(new SolidBrush(t.Item2.Color), new Point[]{p1,p2,p3});
                        }

                    }
                }
                g.DrawRectangle(Pens.Black, 0, 0, viewPortWidth - 1, viewPortHeight - 1);
            }
            catch (Exception ex)
            {
                //System.Diagnostics.Debugger.Break();
            }
        }

        private Color GetXColorFor(int x, int width, Color primary, Color secondary)
        {
            int l = x % width;
            if (l < 0) l += width; // Reverse.
            return (l < width / 2) ? primary : secondary;
        }

        private Color GetYColorFor(int y, double cameraHeight, double yOffset, double lineSpacing)
        {
            double z = 0.0;
            if (y != 0) { z = cameraHeight * Size.Height / (double)y + yOffset; }
            double l = Math.Sin(z * 2.0 * Math.PI / lineSpacing);
            return (l <= 0) ? Color.FromArgb(51, 204, 51) : Color.FromArgb(97, 212, 97);
        }

        private double CalculateViewLocationRelativeToGround(
            double cameraHeightFromGround,
            double pointHeightFromGround,
            double pointDistanceFromCamera,
            double viewDistanceFromCamera)
        {
            double pointOffsetFromHorizon = 0;
            if (pointDistanceFromCamera != 0)
                pointOffsetFromHorizon = (Math.Abs(pointHeightFromGround - cameraHeightFromGround) / (pointDistanceFromCamera)) * viewDistanceFromCamera;

            return (pointHeightFromGround > cameraHeightFromGround)
                ? (cameraHeightFromGround + pointOffsetFromHorizon) : (cameraHeightFromGround - pointOffsetFromHorizon);

        }

        internal static GraphicsPath CreateRoundedRectanglePath(Rectangle rect, int cornerRadius)
        {
            GraphicsPath roundedRect = new GraphicsPath();
            roundedRect.AddArc(rect.X, rect.Y, cornerRadius * 2, cornerRadius * 2, 180, 90);
            roundedRect.AddLine(rect.X + cornerRadius, rect.Y, rect.Right - cornerRadius * 2, rect.Y);
            roundedRect.AddArc(rect.X + rect.Width - cornerRadius * 2, rect.Y, cornerRadius * 2, cornerRadius * 2, 270, 90);
            roundedRect.AddLine(rect.Right, rect.Y + cornerRadius * 2, rect.Right, rect.Y + rect.Height - cornerRadius * 2);

            roundedRect.AddArc(rect.X + rect.Width - cornerRadius * 2, rect.Y + rect.Height - cornerRadius * 2, cornerRadius * 2, cornerRadius * 2, 0, 90);

            roundedRect.AddLine(rect.Right - cornerRadius * 2, rect.Bottom, rect.X + cornerRadius * 2, rect.Bottom);
            roundedRect.AddArc(rect.X, rect.Bottom - cornerRadius * 2, cornerRadius * 2, cornerRadius * 2, 90, 90);
            roundedRect.AddLine(rect.X, rect.Bottom - cornerRadius * 2, rect.X, rect.Y + cornerRadius * 2);
            roundedRect.CloseFigure();
            return roundedRect;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="FirstPersonViewControl"/> class.
        /// </summary>
        public FirstPersonViewControl()
        {
            InitializeComponent();

            _spriteSheets = new List<SimpleSpriteSheet>();

            //_polygons = ObjectParser3D.ParseWavefrontObject(@"Resources\objects\cube.obj");

            // Pull images from Image List
            foreach(var b in spriteImageList.Images)
            {
                _spriteSheets.Add(new SimpleSpriteSheet((Bitmap)b, 32, 32));
            }
             
            //This turns off internal double buffering of all custom GDI+ drawing
            this.SetStyle(ControlStyles.DoubleBuffer, true);
            this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            this.SetStyle(ControlStyles.UserPaint, true);
            this.SetStyle(ControlStyles.ResizeRedraw, true);


            Random r = new Random();
            // Clouds
            for (int i = 0; i < 25; i++)
            {
                int ci = r.Next(0,20);
                Color c = Color.FromArgb(255-ci, 255-ci, 255-ci);
                Sprite cloud = new Sprite();

                cloud.Height = r.Next(50, 300);
                cloud.Width = cloud.Height * 2;
                cloud.LocationX = r.Next(-1800, 1800);
                cloud.LocationY = r.Next(-1800, 1800);  
                cloud.Alpha = (byte)r.Next(150, 254);
                cloud.MovementSpeed = 0.2;
                cloud.SpriteType = SpriteType.Cloud;
                cloud.Levitation = r.Next(400, 600);
                cloud.OrientationAngle = 0.0;
                cloud.Color = c;
                _sprites.Add(cloud);
            }

            // Vegitation
            for (int j = 0; j < 2000; j++)
            {
                int side = r.Next(15, 70);
                Sprite s = new Sprite
                {
                    Width = side,
                    Height = side,
                    LocationX = r.Next(-3600, 3600),
                    LocationY = r.Next(-3600, 3600),
                    OrientationAngle = r.Next(0, 180) * RADIAN_CONVERT,
                    SpriteType = SpriteType.Vegetation,
                    Color = (j % 2 ==0) ? Color.DarkGreen : Color.Green
                };
                _sprites.Add(s);
            }


            for (int j = 0; j < 400; j++)
            {
                int side = 6; //r.Next(4, 10);
                Sprite s = new Sprite
                {
                    Width = side,
                    Height = side,
                    LocationX = r.Next(-1800, 1800),
                    LocationY = r.Next(-1800, 1800),
                    //Levitation = r.Next(0, 20),
                    OrientationAngle = r.Next(0, 180) * RADIAN_CONVERT,
                    SpriteType = SpriteType.Person,
                    SpriteSheet = _spriteSheets[r.Next(0, _spriteSheets.Count - 1)]
                };
                _sprites.Add(s);
            }

            Bitmap house = (Bitmap)Bitmap.FromFile("Resources/sprites/house.png");
            SimpleSpriteSheet simpleHouse = new SimpleSpriteSheet(house,112,66);

            // House
            for (int j = 0; j < 100; j++)
            {
                Sprite s = new Sprite
                {
                    Width = 30,
                    Height = 30 * .69,
                    LocationX = r.Next(-1800, 1800),
                    LocationY = r.Next(-1800, 1800),
                    //Levitation = r.Next(0, 20),
                    OrientationAngle = r.Next(0, 180) * RADIAN_CONVERT,
                    SpriteType = SpriteType.Fixed,
                    SpriteSheet = simpleHouse
                };
                _sprites.Add(s);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="pe"></param>
        protected override void OnPaint(PaintEventArgs pe)
        {
            DrawFirstPersonView(pe.Graphics);
        }

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            /*
            if (disposing)
            {
                //Make sure our brushes are cleaned up
                if (foregroundBrush != null)
                    foregroundBrush.Dispose();

                //Make sure our brushes are cleaned up
                if (backgroundBrush != null)
                    backgroundBrush.Dispose();

                //Make sure our timer is cleaned up
                if (timer != null)
                    timer.Dispose();
            }
            base.Dispose(disposing);
             */
        }

        /// <summary>
        /// Required method for Designer support - do not modify 
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FirstPersonViewControl));
            this.spriteImageList = new System.Windows.Forms.ImageList(this.components);
            this.SuspendLayout();
            // 
            // spriteImageList
            // 
            this.spriteImageList.ImageStream = ((System.Windows.Forms.ImageListStreamer)(resources.GetObject("spriteImageList.ImageStream")));
            this.spriteImageList.TransparentColor = System.Drawing.Color.Transparent;
            this.spriteImageList.Images.SetKeyName(0, "template_1.png");
            this.spriteImageList.Images.SetKeyName(1, "template_2.png");
            this.spriteImageList.Images.SetKeyName(2, "template_3.png");
            this.spriteImageList.Images.SetKeyName(3, "template_4.png");
            this.spriteImageList.Images.SetKeyName(4, "template_5.png");
            this.spriteImageList.Images.SetKeyName(5, "template_6.png");
            this.spriteImageList.Images.SetKeyName(6, "template_7.png");
            this.spriteImageList.Images.SetKeyName(7, "template_8.png");
            this.spriteImageList.Images.SetKeyName(8, "template_9.png");
            this.spriteImageList.Images.SetKeyName(9, "template_10.png");
            this.spriteImageList.Images.SetKeyName(10, "template_11.png");
            this.spriteImageList.Images.SetKeyName(11, "template_12.png");
            this.spriteImageList.Images.SetKeyName(12, "template_13.png");
            this.spriteImageList.Images.SetKeyName(13, "template_14.png");
            this.spriteImageList.Images.SetKeyName(14, "template_15.png");
            this.spriteImageList.Images.SetKeyName(15, "template_16.png");
            this.spriteImageList.Images.SetKeyName(16, "template_17.png");
            this.spriteImageList.Images.SetKeyName(17, "template_18.png");
            this.spriteImageList.Images.SetKeyName(18, "template_19.png");
            this.spriteImageList.Images.SetKeyName(19, "template_20.png");
            this.spriteImageList.Images.SetKeyName(20, "template_21.png");
            this.spriteImageList.Images.SetKeyName(21, "template_22.png");
            this.spriteImageList.Images.SetKeyName(22, "template_23.png");
            this.spriteImageList.Images.SetKeyName(23, "template_24.png");
            this.spriteImageList.Images.SetKeyName(24, "template_25.png");
            this.spriteImageList.Images.SetKeyName(25, "template_26.png");
            this.spriteImageList.Images.SetKeyName(26, "template_27.png");
            this.spriteImageList.Images.SetKeyName(27, "template_28.png");
            this.spriteImageList.Images.SetKeyName(28, "template_29.png");
            this.spriteImageList.Images.SetKeyName(29, "template_30.png");
            this.spriteImageList.Images.SetKeyName(30, "template_31.png");
            this.spriteImageList.Images.SetKeyName(31, "template_32.png");
            this.spriteImageList.Images.SetKeyName(32, "template_33.png");
            this.spriteImageList.Images.SetKeyName(33, "template_34.png");
            this.spriteImageList.Images.SetKeyName(34, "template_35.png");
            this.spriteImageList.Images.SetKeyName(35, "template_36.png");
            this.spriteImageList.Images.SetKeyName(36, "template_37.png");
            this.spriteImageList.Images.SetKeyName(37, "template_38.png");
            this.spriteImageList.Images.SetKeyName(38, "template_39.png");
            this.spriteImageList.Images.SetKeyName(39, "template_40.png");
            this.spriteImageList.Images.SetKeyName(40, "template_41.png");
            this.spriteImageList.Images.SetKeyName(41, "template_42.png");
            this.spriteImageList.Images.SetKeyName(42, "template_43.png");
            this.spriteImageList.Images.SetKeyName(43, "template_44.png");
            this.spriteImageList.Images.SetKeyName(44, "template_45.png");
            this.spriteImageList.Images.SetKeyName(45, "template_46.png");
            this.spriteImageList.Images.SetKeyName(46, "template_47.png");
            this.spriteImageList.Images.SetKeyName(47, "template_48.png");
            this.spriteImageList.Images.SetKeyName(48, "template_49.png");
            this.spriteImageList.Images.SetKeyName(49, "template_50.png");
            this.spriteImageList.Images.SetKeyName(50, "template_51.png");
            this.spriteImageList.Images.SetKeyName(51, "template_52.png");
            this.spriteImageList.Images.SetKeyName(52, "template_53.png");
            this.spriteImageList.Images.SetKeyName(53, "template_54.png");
            this.spriteImageList.Images.SetKeyName(54, "template_55.png");
            this.spriteImageList.Images.SetKeyName(55, "template_56.png");
            this.spriteImageList.Images.SetKeyName(56, "template_57.png");
            this.spriteImageList.Images.SetKeyName(57, "template_58.png");
            this.spriteImageList.Images.SetKeyName(58, "template_59.png");
            this.spriteImageList.Images.SetKeyName(59, "template_60.png");
            this.spriteImageList.Images.SetKeyName(60, "template_61.png");
            this.spriteImageList.Images.SetKeyName(61, "template_62.png");
            this.spriteImageList.Images.SetKeyName(62, "template_63.png");
            this.spriteImageList.Images.SetKeyName(63, "template_64.png");
            this.spriteImageList.Images.SetKeyName(64, "template_65.png");
            this.spriteImageList.Images.SetKeyName(65, "template_66.png");
            this.spriteImageList.Images.SetKeyName(66, "template_67.png");
            this.spriteImageList.Images.SetKeyName(67, "template_68.png");
            this.spriteImageList.Images.SetKeyName(68, "template_69.png");
            this.spriteImageList.Images.SetKeyName(69, "template_70.png");
            this.spriteImageList.Images.SetKeyName(70, "template_71.png");
            this.spriteImageList.Images.SetKeyName(71, "template_72.png");
            // 
            // FirstPersonViewControl
            // 
            this.Name = "FirstPersonView";
            this.Size = new System.Drawing.Size(200, 100);
            this.ResumeLayout(false);

        }
    }
}
